'use strict'
const fs = require('fs')
const os = require('os')
const { spawn } = require('child_process')


let cpuTotalCount = os.cpus().length






let utahaPC = function (options = {}) {
    this.child = []        //  保存生成的子进程对象的对象
    this.childPName = {}   //  子进程名称对象，通常用于创建子进程前检查名字是否重复
    this.childCount = 0     //  成功创建的子进程数量，也用于创建时为未添加名字的子进程添加名字
    this.restartCount = 10  //  默认重启次数
    this.errType = {
        0 : 'File is not exist',
        1 : 'This is a dictionary',
        2 : 'No access',
        3 : 'Fs error',
        4 : 'Spawn Error',
        5 : {
            0 : 'Illegal name',
            1 : 'Duplicate name',
            2 : 'Need name'
        },
        6 : 'No option',
        7 : 'Command and file cannot missing at same time',
        8 : 'Stop error'
    }
    this.state = {
        RUNNING : 'running',
        EXIT: 'exit'
    }
    this.mainProcessState = 'free'
}
/**
 * 
 * tryRunChildProcess的opt结构
 * name     子进程名称
 * file     要运行文件的路径
 * command      要运行的命令
 * parameters   传入的参数，数组格式
 * options      spawn的options
 * restart      子进程创建失败是否重启，值为count  none，该字段将控制count字段是否启用
 * restartCount        重启次数,仅在restart字段存在时生效,默认为10      
 */
utahaPC.prototype.tryRunChildProcess = async function (opt = {}) {
    let childProcess = null
    try {
        // console.log(opt)
        childProcess = spawn(opt.command,opt.trueSpawnArg,opt.options)
        //  添加子进程事件监听
        childProcess.on('exit',(code, signal) => {
            // console.log(`pid为${childProcess.pid}的进程结束，当前进程列表为：`)
            // console.dir(this.child)
            for (let key in this.child) {
                if (this.child[key].pid == childProcess.pid) {
                    this.child[key].state = this.state['EXIT']

                    this.child.splice(key,1)
                }
            }
            
            
        })
    } catch (error) {
        this.handleErr(4)
        return null
    }
    return childProcess 
}
/**
 * opt结构
 * name     子进程名称
 * file     要运行文件的路径
 * command      要运行的命令
 * parameters   传入的参数，数组格式
 * options      spawn的options
 * restart      子进程创建失败是否重启，值为count  none，该字段将控制count字段是否启用
 * restartCount        重启次数,仅在restart字段存在时生效,默认为10        
 * 
 */
utahaPC.prototype.createChild = async function (argArr = []) {
    //  标识当前主进程进入running状态
    this.mainProcessState = 'running'
    if ( Array.isArray(argArr) == false) {  //  如果传进去的不是数组，那么将其改为数组格式
        argArr = [ argArr ]
    }
    if (argArr.length == 0) {   //  如果传入空数组，则不需要执行创建子进程的操作
        this.mainProcessState = 'free'
        return
    }


    for(let i in argArr){
        if (!argArr[i].name || argArr[i].name == '') {  //  如果用户未定义子进程名称，则添加默认子进程名称
            argArr[i].name = `utahaChildProcess${this.childCount++}`
        }
        let checkOptRes = this.checkOpt(argArr[i])
        let count = 0   // 每个子进程当前已经重启的次数
        if(checkOptRes.status){   
            // console.log(argArr[i])
            let childProcess = null
            while (!childProcess && count <= argArr[i].restartCount) {  //  子进程生成和生成失败重启
                
                childProcess = await this.tryRunChildProcess(argArr[i])

                //  测试是否重启子进程：
                // if (count > 0) {
                //     console.log(`子进程${argArr[i].name}第${count}次重启!`)
                // }

                count++
            }
            
            
            
            //  这里还要增加成功创建子进程后将子进程信息保存的处理
            if (childProcess) {
                console.log(argArr[i].name,'子进程创建成功!')
               
                
                let childProcessObject = {
                    name: argArr[i].name,
                    pid: childProcess.pid,
                    childPro: childProcess,
                    state: this.state['RUNNING'],
                    relativeProcess: [],
                }
                this.child.push(childProcessObject)       
                
            }else{
                console.log(argArr[i].name,'子进程创建失败!')
                if (argArr[i].name == `utahaChildProcess${this.childCount}`) {
                    this.childCount--
                }
            }
        }
        else{
            console.log(`名称为${argArr[i].name}的子进程参数检查未通过! Message： ${checkOptRes.Info}`)
        }
    }
    this.mainProcessState = 'free'
}
function _checkChildProcessName( name ) {   //  创建时检查名字子进程名字是否合法,只能使用大小写字母、数字以及下划线开头
    return (/^\b/).test(name)
}
utahaPC.prototype.handleErr = function (errType, info = {}) {
    if (Array.isArray(errType)) {
        console.log(`出现错误！错误类型: ${ this.errType[ errType[0] ][ errType[1] ] }`)
    }else{
        console.log(`出现错误！错误类型: ${ this.errType[errType] }`)
    }
}
utahaPC.prototype.tryReadOption = function (opt) {
    // 函数功能：
    // 1. 这个函数将处理传入参数中的command，但对command和file同时不存在情况在checkOpt中检查
    // 2. 处理是否重启子进程
    // 3. 对file指明的文件可读性检查
    // 4. 生成最终传入spawn参数列表的参数


    //对文件可读性进行检查
    if (opt.file) {
        //  文件目录可以采用相对或绝对路径，但必须要求用户写明

        // 检查路径是否存在
        try {
            if (!fs.existsSync(opt.file)) {
                this.handleErr(0)
                return { status: false, Info: this.errType[0] }
            }
        } catch (error) {
            this.handleErr(3)
            return { status: false, Info: this.errType[3] }
        }


        //  检查路径是否是文件夹
        try {
            let stat = fs.lstatSync(opt.file)
            if (stat.isDirectory()) {
                this.handleErr(1)
                return { status: false, Info: this.errType[1] }
            }
        } catch (error) {   //  这里是捕捉fs.lstatSync的错误
            this.handleErr(3)
            return { status: false, Info: this.errType[3] }
        }

        //  检查路径是否有权限读取
        try {
            fs.accessSync(opt.file, fs.constants.R_OK)
            
        } catch (error) {
            this.handleErr(2)
            return { status: false, Info: this.errType[2] }
        }
    }
    
    // 此时，由于通过了Command file同时不存在性检查，则二者必有一存在，那么就需要根据file字段进行检查来调整command
    if (opt.file && !opt.command) { //  file字段存在，而command字段不存在，则默认将.js结尾的文件用node执行
        (/.js$/).test(opt.file) ? opt.command = 'node' : opt.command = ''
    }

    // 通过了以上对command和file的检查，此时只存在一种情况，即存在command不存在file，
    // 也就是单纯的执行命令，那么根据这个生成真正传入的spawn参数位的参数
    if (opt.file) { 
        opt.trueSpawnArg = [opt.file,...opt.parameters]
    }else{
        opt.trueSpawnArg = opt.parameters
    }

    //  restart字段存在，则restartCount字段有效，且如果restartCount已被规定，则将值设为此值
    if (opt.restart) {  
        switch (opt.restart) {
            case 'count':
                opt.restartCount ? opt.restartCount : opt.restartCount = this.restartCount
                break;
            case 'none':    
                opt.restartCount = 0
                break;
            default:
                opt.restartCount = 0
                break;
        }
    }else{
        opt.restartCount ? {} : opt.restartCount = 0
    }


    

    return { status: true }

    
}
utahaPC.prototype.find = function (name) {  //  以名称查找是否存在该子进程，存在返回true
    if (!name || name == '') return false
    for (const key in this.child) {
        if (this.child[key].name == name) {
            
            return true
        }
    }
    return false
}
/**
 * opt结构
 * name     子进程名称
 * file     要运行文件的路径
 * command      要运行的命令
 * parameters   传入的参数，数组格式
 * options      spawn的options
 * restart      子进程创建失败是否重启
 * restartCount        重启次数,仅在restart字段存在时生效,默认为10        
 * 
 */
utahaPC.prototype.checkOpt = function ( opt ) {  //  创建子进程前检查参数
    if(!opt) return { status: false, Info: this.errType[6]  }
    //  这里，对用户没有输入子进程名字的处理应在utahaPC.prototype.createChild完成
    if(!_checkChildProcessName(opt.name)) return { status: false, Info: this.errType[5][0]  }  //  子进程名字合法性检查未通过则返回false

    if(this.find(opt.name))return { status: false, Info: this.errType[5][1]  }  //  存在重名，则检查未通过
    //  处理参数
    if(opt.parameters){
        if(!Array.isArray(opt.parameters)){  //  非数组参数处理
            opt.parameter = [ opt.parameters ]
        }
    }else{
        opt.parameters = []
    }

    //  检查options字段是否存在，不存在则置为空对象
    if (!opt.options) opt.options = {} 

    //  接下来，处理opt中的command和file同时不存在的情况
    if(!opt.command && !opt.file) return { status: false, Info: 'Command and file cannot missing at same time' }
    


    let tryReadOptionRes = this.tryReadOption(opt)

    if (tryReadOptionRes.status) {
        return { status: true, Info: ''}
    }else{
        return tryReadOptionRes
    }


    
}
utahaPC.prototype.findChild = function (name) { //  以名称查找子进程，返回子进程对象
    //  需要注意的是，他与find不同在，find仅对子进程是否存在做存在性查找，而该方法直接返回子进程对象
    //  找到返回子进程及其他数据的对象，否则返回null
    if (!name || name == '') return null

    for (const key in this.child) {
        if (this.child[key].name == name) {
            return this.child[key]
            
        }
    }
    return null
}
utahaPC.prototype.killChild = function (name) { //  以名称查找并终结子进程
    //  在尝试终结子进程后，它会在5s后再次寻找该进程，如果进程依旧存在，那么他将发送SIGKILL信号
    if (!name || name == '') return this.handleErr([5,2])
    let tempChildProObj = this.findChild(name)
    if (tempChildProObj) {
        tempChildProObj.state = this.state['EXIT']
        try {
            process.kill(tempChildProObj.pid, 'SIGTERM')
            console.log(`子进程${tempChildProObj.name}已终止`)
        } catch (error) {
            this.handleErr(8)
        }
        

        setTimeout(() => {
            if (tempChildProObj.child && tempChildProObj.child.exitCode == null) {
                try {
                    process.kill(tempChildProObj.pid,'SIGKILL')
                    console.log(`子进程${tempChildProObj.name}终止失败，现已强制终止`)
                } catch (error) {
                    this.handleErr(8)
                }
            }
            
        },5000)
    }else{
        console.log(`${name}该进程不存在!`)
    }
}

utahaPC.prototype.killAllChildren = function () {   //  结束所有子进程
    for (const key in this.child) {
        try {
            process.kill(this.child[key].pid,'SIGTERM')
        } catch (error) {
            this.handleErr(8)
        }
    }

    setTimeout(() => {
        if (this.child.length > 0) {    //  前面没有杀死所有子进程
            for (const k in this.child) {   //  那么在这里将强制结束所有子进程
                try {
                    process.kill(this.child[k].pid,'SIGKILL')
                } catch (error) {
                    this.handleErr(8)
                }
            }
        }
    }, 5000)
}


utahaPC.prototype.showChildProcess = function () {      
    // 存在异步问题，上面创建子进程的为异步函数，目前是使用了这么个不太聪明的方式来解决异步问题，
    // 丞相莫急，且看我的
    // 待我处理完其他工作后再来办他
    // message date：2023/2/17

    let intval = setInterval(() => {
        if (this.mainProcessState == 'free') {
                let childArr = []
                for (const key in this.child) {
                    let tempObj = {
                        name: this.child[key].name,
                        pid: this.child[key].pid,
                        state: this.child[key].state,
                    }
                    childArr.push(tempObj)
                }
                console.log('当前仍在运行的子进程列表为:')
                console.log(childArr)
                clearInterval(intval)
        }
    },200)
}


module.exports = utahaPC